package com.example.demo.filter;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.GenericFilterBean;


/**
 * @description: 日志拦截器
 * @copyright: Copyright (c) 2019 迅策科技
 * @author: chasel
 * @version: 1.0 
 * @date: 2019年2月1日 
 * @time: 下午12:28:53
 */
@Component
public class CustomTraceFilter extends GenericFilterBean {
	@Autowired
	private LogUtilsProperties logUtilsProperties;

	private static final Logger log = LoggerFactory.getLogger(CustomTraceFilter.class);
    
    /**
     * POST 方法
     */
    public static final String METHOD_POST = "POST";
    
    /**
     * GET  方法
     */
    public static final String METHOD_GET = "GET";
	
	/**
	 * 不包含的Path的数组
	 */
    private static List<String> excludePath = new ArrayList<>(); 
	
	@Override
	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
		if (!(servletRequest instanceof HttpServletRequest) || !(servletResponse instanceof HttpServletResponse)) {
			throw new ServletException("Filter just supports HTTP requests");
		}
		HttpServletRequest request   = (HttpServletRequest) servletRequest;
		HttpServletResponse response = (HttpServletResponse) servletResponse;
		String url = request.getServletPath();
		try {
		    // 验证是排除, 不记录日志
		    if (checkExcluded(url)){
		        filterChain.doFilter(request, response);
		        return ;
		    }
		    Long benginTime = System.currentTimeMillis();
			// 包装ServletRequest
			TraceServletRequestWrapper requestWrapper  = new TraceServletRequestWrapper(request);
			// 包装ServletResponse
			TraceServletResponseWrapper responseWrapper = new TraceServletResponseWrapper(response);
			// 请求
			filterChain.doFilter(requestWrapper, responseWrapper);
			Long endTime = System.currentTimeMillis();
			// 跟踪请求参数
			String requestBody = traceRequestParam(requestWrapper);
			// 跟踪相应参数
			String responseBody = traceResponseParam(responseWrapper);
			StringBuilder builder = new StringBuilder();
			if (StringUtils.isBlank(requestBody)) {
				requestBody = "{}";
			}
			if (StringUtils.isBlank(responseBody)) {
				responseBody = "{}";
			}
			builder.append("{\"requestURL\":" + url);
			String queryString = requestWrapper.getQueryString();
			if (queryString != null){
			    builder.append("?" + queryString);
			} 
			builder.append(",");
			builder.append("\"requestBody\":" + requestBody + ",");
			builder.append("\"responseBody\":" + responseBody + ",");
			builder.append("\"startTime\":" + benginTime + ",");
			builder.append("\"endTime\":" + endTime + ",");
			builder.append("\"costTimes\":" + (endTime - benginTime));
			builder.append("}");
			log.info(builder.toString());
		} catch (Exception e) {
			log.error("tracer request/response param error!", e);
		}
	}
	
	/**
	 * @description：初始化Bean
	 * @date：2019年4月19日 上午10:31:04
	 * @throws ServletException
	 */
	@Override
	protected void initFilterBean() throws ServletException {
        if (logUtilsProperties == null) {
            return;
        }
        List<String> logExcludePath = logUtilsProperties.getExcludePaths();
        for (String path : logExcludePath) {
            if (StringUtils.isNotBlank(path)) {
                excludePath.add(path.trim());
            }
        }
	}
	
	
	/**
	 * @description：验证Url是否需要过滤
	 * @date：2019年4月9日 下午8:02:26
	 * @param requestUrl
	 * @return
	 */
	public boolean checkExcluded(String requestUrl){
        if (excludePath != null && excludePath.contains(requestUrl)) {
            return true; 
        }
        return false;
	}

    private String traceRequestParam(TraceServletRequestWrapper requestWrapper) throws UnsupportedEncodingException {
		String body   = "";
		String method = requestWrapper.getMethod();
		if (method.equalsIgnoreCase(METHOD_GET)) {
            body = requestWrapper.getQueryString();
        }
		if (method.equalsIgnoreCase(METHOD_POST)) {
			TraceServletInputStream traceInputStream = requestWrapper.getTraceInputStream();
			if (traceInputStream != null) {
				body = new String(traceInputStream.getContent().getBytes(), StandardCharsets.UTF_8);
			} else {
			    String contentType = requestWrapper.getContentType();
			    if (contentType != null && contentType.toLowerCase().startsWith("multipart/form-data")){
			        StringBuffer strBuffer = new StringBuffer("{");
			        Map<String, String[]> map = requestWrapper.getParameterMap();
			        for (Map.Entry<String, String[]> e : map.entrySet()) {
			            if (e.getValue() != null && e.getValue().length > 0) {
                            if (e.getValue().length > 1) {
                                strBuffer.append(e.getKey() + "=" + Arrays.toString(e.getValue()) + ",");
                            } else {
                                strBuffer.append(e.getKey() + "=" + e.getValue()[0] + ",");
                            }
			            }
			        }
			        strBuffer.append("}");
			        body = strBuffer.toString();
			    }
			}
		}
		return body;
	}

	private String traceResponseParam(TraceServletResponseWrapper responseWrapper){
		if (TraceFilterUtils.shouldPrintResponseLog(responseWrapper)){
		    TraceServletOutputStream traceOutputStream = responseWrapper.getTraceOutputStream();		
		    if (traceOutputStream != null && StringUtils.isNotEmpty(traceOutputStream.getContent())) {
				String response = null;
				if (checkPrintResponseSizeSuitable(traceOutputStream)) {
					try {
						response = new String(traceOutputStream.getContent().getBytes(), StandardCharsets.UTF_8);
					} catch (Exception e) {
						log.error("response decode exception", e);
						response = "response decode exception";
					}
				} else {
					response = TraceFilterUtils.RESPONSE_LARGE;
				}
				return response;
		    }		    
		} 
		return null;
	}

	/**
	 *  判断打印日志大小是否合适
	 */
	private boolean checkPrintResponseSizeSuitable(TraceServletOutputStream traceOutputStream) {
		if (logUtilsProperties != null) {
			int logSize = logUtilsProperties.getResponseLogSize() * 1024 * 1024;
			if (traceOutputStream.getContent().getBytes().length > logSize) {
				return false;
			}
		}
		return true;
	}
}